﻿//-----------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Interop;
using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack;
using System.Windows.Input;
using Microsoft.LibraryManagerDemo.Controls;
using System.Windows.Data;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using Microsoft.LibraryManagerDemo.Models;
using Microsoft.WindowsAPICodePack.Dialogs;

namespace Microsoft.LibraryManagerDemo.Presenters
{
    /// <summary>
    /// Provides logic for interacting with the shell view and for managing a shell library.
    /// </summary>
    internal class LibraryPresenter : PresenterBase
    {
        #region CLR Properties

        /// <summary>
        /// Indicates if there is a library open.
        /// </summary>
        public bool IsLibraryOpened
        {
            get { return CurrentLibrary != Library.Null; }
        }

        /// <summary>
        /// Indicates whether folder is selected.
        /// </summary>
        public bool IsFolderSelected
        {
            get
            {
                try
                {
                    if (!IsLibraryOpened)
                    {
                        return false;
                    }

                    var foldersCollectionView = CollectionViewSource.GetDefaultView(CurrentLibrary.Folders);
                    return foldersCollectionView != null &&
                           foldersCollectionView.CurrentItem is Folder;
                }
                catch
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets the view bound to this presenter.
        /// </summary>
        public Window View { get; private set; }

        /// <summary>
        /// A circular list of skin resources.
        /// </summary>
        private CircularList<ResourceDictionary> Skins { get; set; }        

        /// <summary>
        /// Used to break update loop of dependency properties.
        /// </summary>
        private bool IsIgnoreEvent { get; set; }

        /// <summary>
        /// Provide notification when shell library is changed.
        /// </summary>
        private FileSystemWatcher LibraryWatcher { get; set; }        

        #endregion

        #region Dependency Properties

        #region CurrentLibrary
        /// <summary>
        /// Gets the current open library.
        /// </summary>
        public Library CurrentLibrary
        {
            get { return (Library)GetValue(CurrentLibraryProperty); }
            private set { SetValue(CurrentLibraryPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey CurrentLibraryPropertyKey =
            DependencyProperty.RegisterReadOnly(
                "CurrentLibrary",
                typeof(Library),
                typeof(LibraryPresenter),
                new UIPropertyMetadata(Library.Null));

        // Using a DependencyProperty as the backing store for CurrentLibrary.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CurrentLibraryProperty = CurrentLibraryPropertyKey.DependencyProperty;
        #endregion

        #region IsPinnedToNavigationPane
        public bool IsPinnedToNavigationPane
        {
            get { return (bool)GetValue(IsPinnedToNavigationPaneProperty); }
            set { SetValue(IsPinnedToNavigationPaneProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsPinnedToNavigationPane.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsPinnedToNavigationPaneProperty =
            DependencyProperty.Register(
                "IsPinnedToNavigationPane",
                typeof(bool),
                typeof(LibraryPresenter),
                new UIPropertyMetadata(false, IsPinnedToNavigationPanePropertyChanged));

        private static void IsPinnedToNavigationPanePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var presenter = d as LibraryPresenter;
            presenter.PinToNavigationPane(e.NewValue.Equals(true));
        }
        #endregion

        #endregion

        #region Initializers

        /// <summary>
        /// Create a new LibraryManager instance.
        /// </summary>
        /// <param name="view">The view bound to this presenter.</param>
        /// <param name="skin">The default skin.</param>
        public LibraryPresenter(Window view, string defaultSkin)
        {
            InitializeSkin(defaultSkin);
            InitializeView(view);
            InitializeCommandBindings();
            ListenToShellLibraryChange();
        }

        #endregion

        #region Commands

        public ICommand OpenLibraryCommand { get; private set; }
        public ICommand NewLibraryCommand { get; private set; }
        public ICommand AddFolderCommand { get; private set; }
        public ICommand RemoveFolderCommand { get; private set; }
        public ICommand DefaultSaveFolderCommand { get; private set; }
        public ICommand LibraryManageUICommand { get; private set; }
        public ICommand DeleteLibraryCommand { get; private set; }
        public ICommand PickLibraryIconCommand { get; private set; }
        public ICommand PickLibraryTypeCommand { get; private set; }
        public ICommand NextSkinCommand { get; private set; }

        private void InitializeCommandBindings()
        {
            #region Initialize Commands
            OpenLibraryCommand = new RoutedCommand();
            NewLibraryCommand = new RoutedCommand();
            AddFolderCommand = new RoutedCommand();
            RemoveFolderCommand = new RoutedCommand();
            DefaultSaveFolderCommand = new RoutedCommand();
            LibraryManageUICommand = new RoutedCommand();
            DeleteLibraryCommand = new RoutedCommand();
            PickLibraryIconCommand = new RoutedCommand();
            PickLibraryTypeCommand = new RoutedCommand();
            NextSkinCommand = new RoutedCommand();
            #endregion

            var openLibraryCommandBinding = new CommandBinding(OpenLibraryCommand, OpenLibrary);
            View.CommandBindings.Add(openLibraryCommandBinding);

            var newLibraryCommandBinding = new CommandBinding(NewLibraryCommand, NewLibrary);
            View.CommandBindings.Add(newLibraryCommandBinding);

            var deleteLibraryCommandBinding = new CommandBinding(
                DeleteLibraryCommand,
                DeleteLibrary,
                (s, e) => e.CanExecute = IsLibraryOpened);
            View.CommandBindings.Add(deleteLibraryCommandBinding);

            var addFolderCommandBinding = new CommandBinding(
                AddFolderCommand,
                AddFolder,
                (s, e) => e.CanExecute = IsLibraryOpened);
            View.CommandBindings.Add(addFolderCommandBinding);

            var removeFolderCommandBinding = new CommandBinding(
                RemoveFolderCommand,
                RemoveFolder,
                (s, e) => e.CanExecute = IsFolderSelected);
            View.CommandBindings.Add(removeFolderCommandBinding);

            var defaultSaveFolderCommandBinding = new CommandBinding(
                DefaultSaveFolderCommand,
                DefaultSaveFolder,
                CanDefaultSaveFolder);
            View.CommandBindings.Add(defaultSaveFolderCommandBinding);

            var libraryManageUICommandBinding = new CommandBinding(
                LibraryManageUICommand,
                OpenLibraryManage,
                (s, e) => e.CanExecute = IsLibraryOpened);
            View.CommandBindings.Add(libraryManageUICommandBinding);

            var pickLibraryIconCommandBinding = new CommandBinding(
                PickLibraryIconCommand,
                PickLibraryIcon,
                (s, e) => e.CanExecute = IsLibraryOpened);
            View.CommandBindings.Add(pickLibraryIconCommandBinding);

            var pickLibraryTypeCommandCommandBinding = new CommandBinding(
                PickLibraryTypeCommand,
                PickLibraryType,
                (s, e) => e.CanExecute = IsLibraryOpened);
            View.CommandBindings.Add(pickLibraryTypeCommandCommandBinding);

            var nextSkinCommandBinding = new CommandBinding(
                NextSkinCommand,
                (s, e) => NextSkin(),
                (s, e) => e.CanExecute = Skins.Count > 1);
            View.CommandBindings.Add(nextSkinCommandBinding);
        }

        private void OpenLibrary(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var pickLocationDialog = new CommonOpenFileDialog();
                pickLocationDialog.Title = "Pick a library";
                pickLocationDialog.DefaultDirectoryShellContainer = ShellFolder.FromParsingName(ShellLibrary.LibrariesKnownFolder.ParsingName) as ShellContainer;
                pickLocationDialog.InitialDirectoryShellContainer = ShellFolder.FromParsingName(ShellLibrary.LibrariesKnownFolder.ParsingName) as ShellContainer;

                pickLocationDialog.AllowNonFileSystemItems = true;
                pickLocationDialog.IsFolderPicker = true;
                pickLocationDialog.Multiselect = false;

                var result = pickLocationDialog.ShowDialog();
                if (result == CommonFileDialogResult.Cancel)
                {
                    return;
                }

                OpenShellLibrary(pickLocationDialog.FileAsShellObject.Name);
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Open Library", View);
            }
        }

        private void NewLibrary(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var newLibraryPresenter = new NewLibraryPresenter();
                var viewHost = new ViewHost()
                {
                    Owner = View,
                    View = newLibraryPresenter,
                    Width = 300,
                    Height = 150
                };

                if (viewHost.ShowDialog() == true)
                {
                    CreateShellLibrary(newLibraryPresenter.Name);
                }
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "New Library", View);
            }
        }

        private void AddFolder(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var pickLocationDialog = new CommonOpenFileDialog();
                pickLocationDialog.IsFolderPicker = true;
                pickLocationDialog.Title = "Pick a directory to add to the library";
                pickLocationDialog.Multiselect = false;
                pickLocationDialog.AllowNonFileSystemItems = false;

                var result = pickLocationDialog.ShowDialog();
                if (result == CommonFileDialogResult.Cancel)
                {
                    return;
                }

                AddFolderToLibrary(pickLocationDialog.FileName);
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Add Folder", View);
            }
        }

        private void RemoveFolder(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var foldersCollectionView = CollectionViewSource.GetDefaultView(CurrentLibrary.Folders);
                var selectedFolder = foldersCollectionView.CurrentItem as Folder;

                RemoveFolderFromLibrary(selectedFolder.Path);
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Remove Folder", View);
            }
        }

        //DEMO: This method is part of the demo
        private void OpenLibraryManage(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var interopHelper = new WindowInteropHelper(View);

                OpenLibraryManageUI(interopHelper.Handle);
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Open Library Manage", View);
            }
        }

        private void DefaultSaveFolder(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {

                var foldersCollectionView = CollectionViewSource.GetDefaultView(CurrentLibrary.Folders);
                var selectedFolder = foldersCollectionView.CurrentItem as Folder;

                CurrentLibrary.DefaultSaveFolder = selectedFolder == null ? string.Empty : selectedFolder.Path;
                SetDefaultSaveFolder(CurrentLibrary.DefaultSaveFolder);
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Default Save Folder", View);
            }
        }

        private void CanDefaultSaveFolder(object sender, CanExecuteRoutedEventArgs e)
        {
            try
            {

                var foldersCollectionView = CollectionViewSource.GetDefaultView(CurrentLibrary.Folders);
                var selectedFolder = foldersCollectionView.CurrentItem as Folder;

                e.CanExecute = selectedFolder != null &&
                    CurrentLibrary.DefaultSaveFolder != selectedFolder.Path;
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Default Save Folder", View);
            }
        }

        private void DeleteLibrary(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                DeleteShellLibrary();
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Delete Library", View);
            }
        }

        //DEMO: This method is part of the demo
        private void PickLibraryIcon(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var iconPickerPresenter = new IconPickerPresenter();
                var viewHost = new ViewHost()
                {
                    Owner = View,
                    View = iconPickerPresenter,
                    Width = 300,
                    Height = 380
                };

                if (viewHost.ShowDialog() == true)
                {
                    SetIcon(iconPickerPresenter.SelectedIconIdentifier);
                }
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Pick Library Icon", View);
            }
        }

        private void PickLibraryType(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                var libraryTypePresenter = new LibraryTypePresenter();
                var viewHost = new ViewHost()
                {
                    Owner = View,
                    View = libraryTypePresenter,
                    Width = 300,
                    Height = 150
                };

                if (viewHost.ShowDialog() == true)
                {
                    SetFolderType(libraryTypePresenter.FolderType);
                }
            }
            catch (Exception ex)
            {
                ErrorBox.Show(ex.Message, "Select Folder Type", View);
            }
        }

        private void PinToNavigationPane(bool state)
        {
            try
            {
                SetPinnedToNavigationPaneState(state);
                CurrentLibrary.IsPinnedToNavigationPane = state;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Pin To Navigation Pane");
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Initialize skin locations.
        /// </summary>
        private void InitializeSkin(string startupSkin)
        {
            Skins = new CircularList<ResourceDictionary>()
            {
                new ResourceDictionary() { Source = new Uri("pack://application:,,,/Resources/Combustion.xaml") },
                new ResourceDictionary() { Source = new Uri("pack://application:,,,/Resources/Cool.xaml") },
                new ResourceDictionary() { Source = new Uri("pack://application:,,,/Resources/Black.xaml") },
            };

            startupSkin = startupSkin ?? string.Empty;
            // Check if startupSkin exist.
            var defaultResource = Skins.FirstOrDefault(resource => resource.Source.AbsolutePath.EndsWith(startupSkin));
            if (defaultResource != null)
            {
                // Select default skin.
                Skins.Current = defaultResource;
                Application.Current.Resources.MergedDictionaries.Add(defaultResource);
            }
        }

        /// <summary>
        /// Bind presenter to view so this presenter will be the default binding source.
        /// </summary>
        /// <param name="view">The view to bind with this presenter.</param>
        private void InitializeView(Window view)
        {
            this.View = view;
            this.View.DataContext = this;
        }

        /// <summary>
        /// Change to next skin.
        /// </summary>
        private void NextSkin()
        {
            Application.Current.Resources.MergedDictionaries.Remove(Skins.Current);
            Application.Current.Resources.MergedDictionaries.Add(Skins.Next());
        }

        /// <summary>
        /// Open an existing shell library.
        /// </summary>
        /// <param name="shellLibraryName">The name of the library.</param>
        private void OpenShellLibrary(string shellLibraryName)
        {
            using (var shellLibrary = ShellLibrary.Load(shellLibraryName, true))
            {
                UpdateLibraryState(shellLibrary);
            }

            NotifyPropertyChanged("IsLibraryOpened");
        }

        /// <summary>
        /// Make the library manager state coherent with the underline shell library.
        /// </summary>
        /// <param name="shellLibrary">The shell library path.</param>
        private void UpdateLibraryState(ShellLibrary shellLibrary)
        {
            try
            {
                // Break update loop
                IsIgnoreEvent = true;

                CurrentLibrary = new Library(shellLibrary.Name);

                string defaultSaveFolder;
                try
                {
                    defaultSaveFolder = shellLibrary.DefaultSaveFolder;
                }
                catch
                {
                    defaultSaveFolder = string.Empty;
                }

                foreach (ShellFileSystemFolder systemFolder in shellLibrary)
                {
                    bool isDefault = string.Equals(systemFolder.Path, defaultSaveFolder);
                    var folder = new Folder(systemFolder.Name, systemFolder.Path, isDefault);
                    CurrentLibrary.AddFolder(folder);

                    if (folder.IsDefault)
                    {
                        CurrentLibrary.DefaultSaveFolder = folder.Path;
                    }
                }

                CurrentLibrary.IsPinnedToNavigationPane = shellLibrary.IsPinnedToNavigationPane;

                try
                {
                    string iconPath = shellLibrary.IconResourceId.ReferencePath;
                    //CurrentLibrary.ShellIcon = string.IsNullOrEmpty(iconPath) ? null : CoreHelpers.GetIcon(iconPath);
                    CurrentLibrary.ShellIcon = string.IsNullOrEmpty(iconPath) ? null : new BitmapImage(new Uri(iconPath));
                }
                catch
                {
                }

                try
                {
                    CurrentLibrary.FolderType = shellLibrary.LibraryType;
                }
                catch
                {
                }
            }
            finally
            {
                IsIgnoreEvent = false;
            }
        }

        /// <summary>
        /// Create new shell library instance.
        /// </summary>
        /// <param name="name">the name of the library.</param>
        private void CreateShellLibrary(string name)
        {
            using (var shellLibrary = new ShellLibrary(name, false))
            {
                CurrentLibrary = new Library(name);
            }

            NotifyPropertyChanged("IsLibraryOpened");
        }

        /// <summary>
        /// Use <see="FileSystemWatcher"> to update the library manager state whenever there is a change in the underline shell library.
        /// </summary>
        private void ListenToShellLibraryChange()
        {
            if (LibraryWatcher != null)
            {
                LibraryWatcher.Dispose();
            }
            string librariesPath = Path.Combine(
                Environment.GetFolderPath(
                    Environment.SpecialFolder.ApplicationData),
                    ShellLibrary.LibrariesKnownFolder.RelativePath);


            LibraryWatcher = new FileSystemWatcher(librariesPath);
            LibraryWatcher.NotifyFilter = NotifyFilters.LastWrite;
            LibraryWatcher.Filter = "*";
            LibraryWatcher.IncludeSubdirectories = true;

            LibraryWatcher.Changed += (s, e) =>
            {
                //cross thread call
                this.Dispatcher.Invoke(new Action(() =>
                {
                    if (string.IsNullOrEmpty(CurrentLibrary.Name))
                    {
                        return;
                    }

                    try
                    {
                        //We open the library with write permissions for ResolveFolder
                        using (var shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
                        {
                            UpdateLibraryState(shellLibrary);
                        }
                    }
                    catch (Exception exp)
                    {
                        System.Diagnostics.Trace.WriteLine("Could not update the library:" + CurrentLibrary.Name + " state, Error: " + exp.Message);
                    }
                }));
            };
            LibraryWatcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// Add a folder to the shell library.
        /// </summary>
        /// <param name="folderPath">the folder path.</param>
        private void AddFolderToLibrary(string folderPath)
        {
            using (var shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
            {
                shellLibrary.Add(folderPath);
            }
        }


        /// <summary>
        /// Remove a folder from the shell library.
        /// </summary>
        /// <param name="folderPath">the directory path to remove.</param>
        private void RemoveFolderFromLibrary(string folderPath)
        {
            using (ShellLibrary shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
            {
                shellLibrary.Remove(folderPath);
            }
        }

        //DEMO: This method is part of the demo
        /// <summary>
        /// Show the explorer library manager user interface.
        /// </summary>
        /// <param name="hOwnerWnd">the parent window handle.</param>
        private void OpenLibraryManageUI(IntPtr hOwnerWnd)
        {
            ShellLibrary.ShowManageLibraryUI(
                CurrentLibrary.Name,
                hOwnerWnd,
                "The Windows Shell Explorer Library Manager",
                "Manage the " + CurrentLibrary.Name,
                true);
        }

        /// <summary>
        /// Delete the library.
        /// </summary>
        private void DeleteShellLibrary()
        {
            string librariesPath = Path.Combine(
                    Environment.GetFolderPath(
                    Environment.SpecialFolder.ApplicationData),
                        ShellLibrary.LibrariesKnownFolder.RelativePath);

            string libraryPath = Path.Combine(librariesPath, CurrentLibrary.Name);
            string libraryFullPath = Path.ChangeExtension(libraryPath, "library-ms");

            File.Delete(libraryFullPath);

            try
            {
                IsIgnoreEvent = true;

                CurrentLibrary = Library.Null;
                LibraryWatcher.Dispose();
                LibraryWatcher = null;
            }
            finally
            {
                IsIgnoreEvent = false;
            }
        }

        /// <summary>
        /// Set the shell library default folder.
        /// <remarks>Clients of this class use the <see cref="DefaultSaveFolder"/> property</remarks>.
        /// </summary>
        /// <param name="folderPath">The path to the default folder.</param>
        private void SetDefaultSaveFolder(string folderPath)
        {
            if (IsIgnoreEvent)
                return;

            using (ShellLibrary shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
            {
                shellLibrary.DefaultSaveFolder = folderPath;
            }
        }

        /// <summary>
        /// Set the folder type template.
        /// </summary>
        /// <param name="folderType">The type of the folder template.</param>
        private void SetFolderType(LibraryFolderType folderType)
        {
            if (IsIgnoreEvent)
                return;

            using (var shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
            {
                shellLibrary.LibraryType = folderType;
            }
        }

        /// <summary>
        /// Set the pinning state in the explorer navigation pane.
        /// <remarks>Clients of this class use the <see cref="PinnedToNavigationPaneState"/> property.</remarks>
        /// </summary>
        /// <param name="state">The new pinning state.</param>
        private void SetPinnedToNavigationPaneState(bool state)
        {
            if (IsIgnoreEvent)
            {
                return;
            }

            using (ShellLibrary shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
            {
                shellLibrary.IsPinnedToNavigationPane = state;
            }
        }

        //DMEO: This method is part of the demo
        /// <summary>
        /// Set the icon of the library.
        /// </summary>
        /// <param name="iconId">the Shell stock icon ID.</param>
        private void SetIcon(int iconId)
        {
            using (var shellLibrary = ShellLibrary.Load(CurrentLibrary.Name, false))
            {
                shellLibrary.IconResourceId = new IconReference(Helper.GetIcon(iconId));
                CurrentLibrary.ShellIcon = shellLibrary.Thumbnail.BitmapSource;
            }
        }

        /// <summary>
        /// This helper class provide interop gateway to native shell API that are not exported in the Vista Bridge.
        /// </summary>
        private class Helper
        {
            [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            private struct StockIconInfo
            {
                internal UInt32 StuctureSize;
                internal IntPtr Handle;
                internal Int32 ImageIndex;
                internal Int32 Identifier;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
                internal string Path;
            }

            public static string GetIcon(int identifier)
            {
                StockIconInfo info = new StockIconInfo();
                info.StuctureSize = (UInt32)System.Runtime.InteropServices.Marshal.SizeOf(typeof(StockIconInfo));

                int hResult = SHGetStockIconInfo(identifier, 0, ref info);

                if (hResult != 0)
                    throw new System.ComponentModel.Win32Exception("SHGetStockIconInfo execution failure " + hResult.ToString());

                return info.Path + "," + info.Identifier;
            }

            [DllImport("Shell32.dll", CharSet = CharSet.Unicode,
            ExactSpelling = true, SetLastError = false)]
            private static extern int SHGetStockIconInfo(
                int identifier,
                int flags,
                ref StockIconInfo info);
        }

        #endregion
    }
}
